Skip to content

fix(flags): make recent feature flags show and link correctly - #110227

Open
phillram wants to merge 6 commits into
masterfrom
posthog/fix-recent-flag-labels
Open

phillram wants to merge 6 commits into
masterfrom
posthog/fix-recent-flag-labels

Conversation

@phillram

@phillram phillram commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A survey author who links a feature flag sees blank rows in the flag picker's Recent category: no key, no name, nothing to tell one flag from another. The flags themselves are fine, and the main Feature Flags list labels them correctly.

A feature flag keeps its description in name (a legacy misnomer), and most flags leave the description empty. The picker's Recent category had nothing else to label a row with:

  • A pick stored as a recent kept only name and id, so the flag key never reached storage.
  • The recent row labelled itself from name alone and never asked the source group for a label.
  • The definition popover titled itself from name too, so it read "(empty string)" for any flag with no description, in the Recent list and in the main flag list.

The picker also handed the stored summary to callers as if it were the flag, which broke the link rather than only its label. A replay trigger persists {id, key} to the team and the key was undefined, so the trigger matched nothing. The survey editor stored the summary as linked_flag, leaving the variant selector with no flag to read. That is why a flag picked from Recent did not take, while the same flag picked from the Feature Flags list did.

Reported in support ticket 76386.

Changes

  • A recent flag row now shows the flag key, and its hover panel titles itself with the key.
  • A stored recent keeps the fields a group labels its rows with (key, title, short_id), through one shared pickMinimalRecentItem used by both the classic picker and the rebuilt menu.
  • Flag recents already stored without a key are dropped from the list, so blank rows go away now instead of waiting out the 30 day expiry.
  • The same gap closes for the other groups that do not label a row with name: a saved replay filter keeps derived_name, a group keeps group_key, a notebook keeps title and short_id.
  • A picked flag also hands its key back to the caller again, which is what FlagSelector labels its button with while the flag loads. That button serves surveys, experiments, early access features, product tours, replay triggers and ingestion controls, and the flag dependency picker on a feature flag reads the same group.
  • A flag picked from Recent now links correctly. pickedFeatureFlag resolves the id, requires a key, and returns the flag only when the picked row really is one; the third onChange argument is optional, so a caller that needs the whole flag loads it by id. The product tour field already did that, and the survey editor now does too.
  • Mechanical: comments in FlagSelector that described the old storage shape.

Before and after, same Storybook story (a flag with no description, picked and then viewed in Recent):

before_wide

after_wide

How did you test this code?

Test rationale: Added cases, one per defect that no existing test covered. pickMinimalRecentItem in recentTaxonomicFiltersLogic.test.ts locks the stored shape, because the nearest existing cases only record items whose label is name. A render case in TaxonomicFilterKeyOnly.test.tsx asserts the Recent row text, which is the only level that catches the row label; it sits beside the other recent-row render cases in that file. One case in suggestedContextFilters.test.ts covers dropping a keyless flag recent.

pickedFeatureFlag is covered in FlagSelector.test.tsx: a summary row withholds the flag, a full row returns it, and a row with no key is not a selection.

Ran locally: the Jest suites for TaxonomicFilter, DefinitionPopover, PropertyFilters, FlagSelector, surveys, the experiment form and ingestion controls, and the repo TypeScript check. Checked the before and after by hand in Storybook through a headless browser, which is where the screenshots come from; the story was scratch and is not in the diff. Not run: the rest of the frontend suite and anything backend, since the change is frontend only.

Release status

  • No feature flag controls this change

Automatic notifications

  • Publish to changelog?

Docs update

No docs cover the picker's Recent category, so no docs change.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: PostHog Desktop, Claude Opus 5

  • Skills invoked: /modifying-taxonomic-filter, /writing-tests, /writing-code-comments, /writing-pr-descriptions.
  • The storage fix alone looked complete until the Storybook check showed the row still blank, which exposed the two display defects. That is why the diff reaches InfiniteList and DefinitionPopoverContents as well.
  • The reporter then said the flag would not take at all when picked from Recent, only that it looked blank. Tracing the picked object through each caller found the second defect, which is the one that broke the link. The display fix alone would have left it.
  • Both recent record sites are changed, so the classic picker and the rebuilt menu stay in step.
  • No duplicate: gh pr list --state open --search over flag and taxonomic recent terms found no open PR for this.
  • Public artifact: the ticket screenshot and the reporter's project were not used in the diff. The flag keys in the tests and screenshots are invented.

Created with PostHog Desktop

A feature flag keeps its description in `name`, and most flags leave it empty, so the taxonomic filter had nothing to label a flag with in its Recent category.

Two causes. A pick stored as a recent kept only `name` and `id`, which dropped the flag key. The recent row then labelled itself from `name` alone and never asked the source group for a label. The definition popover titled itself from `name` too, so it read "(empty string)" for any flag with no description, in the Recent list and in the main flag list.

Recents now keep the fields a group labels its rows with, the recent row falls back to the group's `getName`, and the popover title does the same. Flag recents already stored without a key are dropped, so the blank rows go away instead of waiting out the 30 day expiry.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805
@phillram phillram self-assigned this Oct 1, 2026
@trunk-io

trunk-io Bot commented Oct 1, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Complexity (TypeScript) — 17 functions above the limit (max 63)

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

Function Location Complexity Limit
DefinitionView frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:118 63 10
InfiniteListRow frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:437 44 10
SurveyEdit frontend/src/scenes/surveys/SurveyEdit.tsx:265 44 10
renderItemContents frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:198 39 10
InfiniteListEmptyState frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:707 26 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:1162 23 10
DefinitionEdit frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:588 21 10
selectItem frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:2602 20 10
resolveTaxonomicGroupTypes frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts:207 18 10
infiniteListResultsReceived frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:2794 17 10
InfiniteList frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:884 16 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts:480 15 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts:36 15 10
<anonymous> frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:491 14 10
isDuplicateRecentFilter frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts:147 13 10
ControlledDefinitionPopover frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:714 11 10
expandRecentsForDisplay frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts:104 11 10
✅ Duplication (Python) — clean

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

🚨 Comment density — 17% of added code lines are comments (48 of 289)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
frontend/src/lib/components/FlagSelector.tsx 16 43
frontend/src/scenes/surveys/SurveyEdit.tsx 10 50
frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts 7 17
frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx 4 5
frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts 4 18
frontend/src/lib/components/FlagSelector.test.tsx 3 59
frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx 3 4
frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.test.ts 1 41

This check does not block merging. It updates on every push and clears when the share drops.

✅ Bundle size — 🟢 -322.9 KiB (-0.4%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 69.79 MiB · 🟢 -322.9 KiB (-0.4%)

File Size Δ vs base
render-query/src/render-query/render-query.js 18.76 MiB 🟢 -1.46 MiB (-7.2%)
exporter/src/lib/hog.js 435.1 KiB 🔺 +435.1 KiB (new)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.90 MiB 🔺 +202.9 KiB (+5.4%)
posthog-app/src/scenes/saved-insights/SavedInsights.js 91.4 KiB 🔺 +79.5 KiB (+668.4%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-B4-N-ucD.js 76.7 KiB 🔺 +76.7 KiB (new)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-B4-N-ucD.js 76.7 KiB 🔺 +76.7 KiB (new)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-CAbukJW8.js removed 🟢 -76.7 KiB (-100.0%)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-CAbukJW8.js removed 🟢 -76.7 KiB (-100.0%)
exporter/_parent/products/subscriptions/frontend/components/Subscriptions/SubscriptionsModal.js 74.3 KiB 🔺 +74.3 KiB (new)
posthog-app/_parent/products/canvas/frontend/scene/CanvasScene.js 69.1 KiB 🔺 +69.1 KiB (new)
posthog-app/src/scenes/project-homepage/ProjectHomepage.js 1.6 KiB 🟢 -56.8 KiB (-97.2%)
posthog-app/src/scenes/AuthenticatedShell.js 268.1 KiB 🟢 -48.0 KiB (-15.2%)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 92.2 KiB 🔺 +46.4 KiB (+101.5%)
posthog-app/_parent/products/canvas/frontend/sidePanel/CanvasSidePanel.js 45.9 KiB 🔺 +45.9 KiB (new)
posthog-app/src/scenes/project-homepage/ai-first/AiFirstHomepage.js 43.4 KiB 🔺 +43.4 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 91.9 KiB 🔺 +38.3 KiB (+71.4%)
posthog-app/src/layout/today/TodaySpacesPane.js 31.9 KiB 🔺 +31.9 KiB (new)
infrastructure-admin/_parent/products/tasks/frontend/infrastructure/infrastructure-admin.js 606.9 KiB 🔺 +28.6 KiB (+4.9%)
posthog-app/_parent/products/dashboards/frontend/widgets/AddWidgetModal.js 24.2 KiB 🔺 +24.2 KiB (new)
exporter/_parent/products/dashboards/frontend/widgets/AddWidgetModal.js 22.0 KiB 🔺 +22.0 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorerScene.js 21.6 KiB 🔺 +21.6 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -19.5 KiB (-100.0%)
posthog-app/src/scenes/project-homepage/today/TodayReportPage.js 19.0 KiB 🔺 +19.0 KiB (new)
posthog-app/_parent/products/data_modeling/frontend/ModelsScene.js 58.3 KiB 🔺 +18.8 KiB (+47.5%)
exporter/src/lib/components/TerraformExporter/TerraformExportModal.js 17.3 KiB 🔺 +17.3 KiB (new)
posthog-app/src/scenes/views/Views.js 10.4 KiB 🔺 +10.4 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayHome.js 10.3 KiB 🔺 +10.3 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCardChart.js 10.2 KiB 🔺 +10.2 KiB (new)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/components/ScannerScoutsTab.js 18.9 KiB 🟢 -10.1 KiB (-34.9%)
posthog-app/src/scenes/web-analytics/WebAnalyticsScene.js 253.6 KiB 🔺 +10.0 KiB (+4.1%)
posthog-app/_parent/products/tasks/frontend/spaces/NewSessionScene.js 9.4 KiB 🔺 +9.4 KiB (new)
posthog-app/src/scenes/dashboard/addInsightToDashboardModal/AddInsightToDashboardModal.js 8.6 KiB 🔺 +8.6 KiB (new)
posthog-app/src/layout/today/TodayViewsSidebar.js 8.5 KiB 🔺 +8.5 KiB (new)
posthog-app/src/layout/today/TodaySessionHoverCard.js 8.3 KiB 🔺 +8.3 KiB (new)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-react-shadow.esm-B4-N-ucD.js 7.8 KiB 🔺 +7.8 KiB (new)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-react-shadow.esm-CAbukJW8.js removed 🟢 -7.8 KiB (-100.0%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js 4.7 KiB 🟢 -7.6 KiB (-62.1%)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorersScene.js 13.6 KiB 🟢 -7.5 KiB (-35.6%)
posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart.js 9.3 KiB 🟢 -7.2 KiB (-43.8%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene.js 11.3 KiB 🟢 -7.1 KiB (-38.6%)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -6.8 KiB (-100.0%)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCard.js 6.8 KiB 🔺 +6.8 KiB (new)
exporter/src/scenes/dashboard/addInsightToDashboardModal/AddInsightToDashboardModal.js 6.7 KiB 🔺 +6.7 KiB (new)
posthog-app/src/layout/today/TodaySpaceHoverCard.js 6.7 KiB 🔺 +6.7 KiB (new)
posthog-app/src/scenes/activity/explore/EventDetails.js 6.5 KiB 🔺 +6.5 KiB (new)
posthog-app/_parent/products/tasks/frontend/spaces/NewSpaceDialog.js 5.5 KiB 🔺 +5.5 KiB (new)
posthog-app/src/lib/components/ActivityLog/describers.js 162.5 KiB 🟢 -5.3 KiB (-3.1%)
posthog-app/_parent/products/ai_observability/frontend/aiObservabilityColumnRenderers.js 5.2 KiB 🔺 +5.2 KiB (new)
posthog-app/_parent/products/subscriptions/frontend/components/Subscriptions/SubscriptionsModal.js 5.0 KiB 🔺 +5.0 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCardMetric.js 4.9 KiB 🔺 +4.9 KiB (new)
posthog-app/_parent/products/canvas/frontend/newCanvas/CanvasNewScene.js 4.3 KiB 🔺 +4.3 KiB (new)
posthog-app/src/lib/components/Sharing/SharingModal.js 3.8 KiB 🔺 +3.8 KiB (new)
posthog-app/_parent/products/business_knowledge/frontend/scenes/settings/BusinessKnowledgeSettingsScene.js 13.1 KiB 🟢 -3.7 KiB (-22.0%)
posthog-app/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene.js 12.3 KiB 🟢 -3.7 KiB (-23.0%)
posthog-app/src/scenes/settings/SettingsMap.js 12.2 KiB 🟢 -3.7 KiB (-23.1%)
posthog-app/src/scenes/settings/SettingsScene.js 15.8 KiB 🟢 -3.7 KiB (-18.9%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene.js 14.4 KiB 🟢 -3.6 KiB (-20.1%)
exporter/src/scenes/activity/explore/EventDetails.js 3.6 KiB 🔺 +3.6 KiB (new)
posthog-app/_parent/products/error_tracking/frontend/posthogAi/ErrorTrackingWidget.js 13.5 KiB 🟢 -3.5 KiB (-20.5%)
posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene.js 70.3 KiB 🔺 +3.4 KiB (+5.1%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ScannerEditorScene.js 59.7 KiB 🟢 -3.4 KiB (-5.3%)
posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene.js 61.1 KiB 🔺 +3.3 KiB (+5.6%)
posthog-app/_parent/products/tasks/frontend/spaces/SpaceScene.js 71.6 KiB 🟢 -3.2 KiB (-4.3%)
posthog-app/src/scenes/debug/hog/HogRepl.js 8.5 KiB 🟢 -3.1 KiB (-26.9%)
posthog-app/src/scenes/dashboard/Dashboard.js 11.5 KiB 🟢 -3.1 KiB (-21.0%)
exporter/_parent/products/ai_observability/frontend/aiObservabilityColumnRenderers.js 3.0 KiB 🔺 +3.0 KiB (new)
posthog-app/src/scenes/dashboard/dashboards/Dashboards.js 38.7 KiB 🟢 -2.9 KiB (-6.9%)
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene.js 187.4 KiB 🔺 +2.5 KiB (+1.4%)
posthog-app/src/scenes/notebooks/NotebookScene.js 34.7 KiB 🟢 -2.4 KiB (-6.5%)
exporter/src/lib/components/Sharing/SharingModal.js 2.3 KiB 🔺 +2.3 KiB (new)
posthog-app/_parent/products/conversations/frontend/components/ComposeTicket/ComposeTicketModal.js 3.5 KiB 🔺 +2.1 KiB (+153.2%)
posthog-app/src/queries/nodes/WebVitals/WebVitals.js 15.9 KiB 🟢 -2.1 KiB (-11.8%)
posthog-app/src/queries/Query/Query.js 9.6 KiB 🟢 -2.1 KiB (-18.2%)
posthog-app/src/scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene.js 15.4 KiB 🟢 -2.1 KiB (-12.0%)
posthog-app/_parent/products/persons/frontend/pages/PersonsScene.js 14.9 KiB 🟢 -2.1 KiB (-12.2%)
posthog-app/src/layout/today/TodayToolsSidebar.js 2.1 KiB 🔺 +2.1 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/CreateInsightWidget.js 10.3 KiB 🟢 -2.1 KiB (-16.6%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/QueryWidget.js 10.4 KiB 🟢 -2.1 KiB (-16.6%)
posthog-app/src/layout/today/TodayChatHoverCard.js 2.0 KiB 🔺 +2.0 KiB (new)
posthog-app/src/scenes/debug/DebugScene.js 28.3 KiB 🟢 -1.9 KiB (-6.4%)
posthog-app/_parent/products/groups/frontend/pages/Groups.js 12.9 KiB 🟢 -1.9 KiB (-13.0%)
posthog-app/_parent/products/signals/frontend/inbox/components/detail/ReportAiPanel.js 13.3 KiB 🟢 -1.9 KiB (-12.7%)
posthog-app/src/scenes/activity/explore/SessionsScene.js 13.3 KiB 🟢 -1.9 KiB (-12.6%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene.js 11.1 KiB 🟢 -1.9 KiB (-14.6%)
posthog-app/src/layout/panel-layout/navbar/tabs/NavTabChat.js 19.4 KiB 🟢 -1.9 KiB (-8.9%)
posthog-app/src/layout/today/TodayLibrarySidebar.js 1.8 KiB 🔺 +1.8 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/SidePanelRunnerImpl.js 15.9 KiB 🟢 -1.8 KiB (-10.3%)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/EmbeddedTaskComposerImpl.js 11.1 KiB 🟢 -1.8 KiB (-14.1%)
posthog-app/src/scenes/cohorts/Cohort.js 44.1 KiB 🟢 -1.8 KiB (-4.0%)
posthog-app/src/scenes/experiments/SharedMetrics/SharedMetric.js 18.7 KiB 🟢 -1.8 KiB (-8.8%)
exporter/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js 3.1 KiB 🟢 -1.8 KiB (-36.8%)
posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene.js 135.4 KiB 🟢 -1.8 KiB (-1.3%)
exporter/_parent/products/alerts/frontend/views/EditAlertModal.js 114.5 KiB 🔺 +1.7 KiB (+1.5%)
posthog-app/_parent/products/endpoints/frontend/EndpointsScene.js 37.2 KiB 🟢 -1.7 KiB (-4.4%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene.js 99.8 KiB 🔺 +1.7 KiB (+1.7%)
posthog-app/_parent/products/ai_observability/frontend/playground/AIObservabilityPlaygroundScene.js 46.6 KiB 🔺 +1.7 KiB (+3.7%)
posthog-app/src/scenes/product-tours/ProductTour.js 179.8 KiB 🟢 -1.7 KiB (-0.9%)
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagScene.js 36.9 KiB 🟢 -1.7 KiB (-4.3%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/playground/BusinessKnowledgePlaygroundScene.js 25.1 KiB 🟢 -1.6 KiB (-6.1%)
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagsScene.js 16.9 KiB 🟢 -1.6 KiB (-8.8%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/EvaluationsScene.js 29.0 KiB 🟢 -1.6 KiB (-5.3%)
posthog-app/_parent/products/endpoints/frontend/EndpointScene.js 61.3 KiB 🟢 -1.6 KiB (-2.6%)
posthog-app/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene.js 11.6 KiB 🟢 -1.6 KiB (-12.1%)
posthog-app/_parent/products/feature_flags/frontend/staff/FeatureFlagsStaffToolsScene.js 60.6 KiB 🔺 +1.5 KiB (+2.5%)
posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeature.js 10.9 KiB 🟢 -1.5 KiB (-11.8%)
posthog-app/_parent/products/workflows/frontend/Broadcasts/BroadcastScene.js 83.0 KiB 🔺 +1.4 KiB (+1.8%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene.js 94.3 KiB 🔺 +1.4 KiB (+1.5%)
posthog-app/_parent/products/logs/frontend/LogsScene.js 47.5 KiB 🟢 -1.3 KiB (-2.7%)
posthog-app/_parent/products/workflows/frontend/Broadcasts/BroadcastsScene.js 14.1 KiB 🟢 -1.3 KiB (-8.6%)
posthog-app/_parent/products/actions/frontend/pages/Action.js 11.3 KiB 🟢 -1.3 KiB (-10.5%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityScene.js 167.2 KiB 🟢 -1.3 KiB (-0.8%)
posthog-app/_parent/products/persons/frontend/pages/PersonScene.js 45.2 KiB 🔺 +1.3 KiB (+2.9%)
posthog-app/src/scenes/notebooks/NotebookCanvasScene.js 25.4 KiB 🔺 +1.3 KiB (+5.2%)
posthog-app/src/scenes/notebooks/NotebookPanel/NotebookPanel.js 27.1 KiB 🔺 +1.3 KiB (+4.9%)
posthog-app/_parent/products/groups/frontend/pages/Group.js 35.7 KiB 🔺 +1.3 KiB (+3.7%)
posthog-app/_parent/products/workflows/frontend/WorkflowsScene.js 25.5 KiB 🟢 -1.2 KiB (-4.6%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsAccountScene/CustomerAnalyticsAccountScene.js 56.5 KiB 🟢 -1.2 KiB (-2.1%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptScene.js 52.5 KiB 🟢 -1.2 KiB (-2.2%)
posthog-app/src/scenes/data-management/definition/DefinitionView.js 36.0 KiB 🟢 -1.2 KiB (-3.2%)
posthog-app/src/scenes/experiments/Experiment.js 295.7 KiB 🔺 +1.2 KiB (+0.4%)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/src/scenes/hog-functions/HogFunctionScene.js 55.6 KiB 🟢 -1.2 KiB (-2.1%)
posthog-app/src/lib/hog.js 1.1 KiB 🔺 +1.1 KiB (new)
posthog-app/src/scenes/session-recordings/kiosk/SessionRecordingsKiosk.js 24.5 KiB 🟢 -1.1 KiB (-4.4%)
posthog-app/src/scenes/session-recordings/player/modal/SessionPlayerModal.js 17.1 KiB 🟢 -1.1 KiB (-6.2%)
posthog-app/_parent/products/replay_vision/frontend/observations/ObservationRecording.js 16.2 KiB 🟢 -1.1 KiB (-6.5%)
posthog-app/src/scenes/session-recordings/detail/SessionRecordingDetail.js 16.5 KiB 🟢 -1.1 KiB (-6.4%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/src/scenes/session-recordings/file-playback/SessionRecordingFilePlaybackScene.js 19.2 KiB 🟢 -1.1 KiB (-5.5%)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/src/scenes/sessions/SessionProfileScene.js 29.4 KiB 🟢 -1.1 KiB (-3.6%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene.js 178.2 KiB 🟢 -1.1 KiB (-0.6%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation.js 88.5 KiB 🔺 +1.1 KiB (+1.2%)
posthog-app/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.js 21.5 KiB 🟢 -1.1 KiB (-4.7%)
posthog-app/src/scenes/insights/InsightScene.js 49.0 KiB 🟢 -1.0 KiB (-2.1%)
posthog-app/src/lib/components/TerraformExporter/TerraformExportModal.js 1.0 KiB 🔺 +1.0 KiB (new)
posthog-app/src/scenes/activity/explore/EventsScene.js 12.9 KiB 🟢 -1.0 KiB (-7.4%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.63 MiB · 22 files 🔺 +22.3 KiB (+1.4%) █████████░ 88.7% of 1.84 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.72 MiB · 661 files 🔺 +159.2 KiB (+4.4%) █████████░ 92.3% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,409 files 🟢 -12.5 KiB (-0.2%) █████████░ 91.0% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.67 MiB · 3,392 files (no base measurement) ███████░░░ 71.8% of 13.48 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.61 MiB · 2,417 files (no base measurement) █████████░ 88.6% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.29 MiB · 3,244 files (no base measurement) ███████░░░ 73.5% of 12.64 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.12 MiB · 4,130 files (no base measurement) ████████░░ 77.1% of 15.72 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
100.5 KiB src/lib/api.ts
92.7 KiB src/products.tsx
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
40.1 KiB src/lib/utils/eventUsageLogic.ts
38.7 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
33.9 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
29.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
315.5 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
181.8 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.20 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.20 MiB · 19 files 🔺 +11.3 KiB (+0.5%) ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 44 files 🔺 +9.6 KiB (+0.4%) n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
835.6 KiB dist/toolbar/toolbar-app-IDXKA4WE.css
657.5 KiB dist/toolbar/chunk-chunk-BALLB66W.js
259.4 KiB dist/toolbar/chunk-chunk-7JWMBALG.js
138.2 KiB dist/toolbar/chunk-chunk-RXJG6CZC.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-YIYWW6XJ.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-MM7MZI2L.js
21.0 KiB dist/toolbar/chunk-chunk-EZFR5QGQ.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🟢 -1.89 MiB (-0.2%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 960.45 MiB · 🟢 -1.89 MiB (-0.2%)

✅ Playwright — all passed

All tests passed.

View test results →

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Fixes feature flag display and selection in UI components.

The PR does not appear safe to merge until recent flag fetches cannot overwrite later survey selections and flag popover titles consistently use the key.

Reviews (2) · Last reviewed commit: "fix(flags): stop passing a recent flag s..."

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (13)
.cursor/rules/react-typescript.mdc — auto-discovered
.agents/skills/using-kea-disposables/SKILL.md — configured
.agents/skills/writing-ui-components/SKILL.md — configured
.agents/security.md — configured
docs/published/handbook/engineering/type-system.md — configured
.agents/skills/writing-tests/SKILL.md — configured
.agents/skills/modifying-taxonomic-filter/SKILL.md — configured
.agents/skills/adopting-generated-api-types/SKILL.md — configured
.agents/skills/placing-product-frontend-code/SKILL.md — configured
.claude/commands/conventions.md — configured
.agents/skills/writing-kea-logics/SKILL.md — configured
.agents/skills/writing-user-facing-copy/SKILL.md — configured
.agents/skills/writing-code-comments/SKILL.md — configured
📝 Walkthrough

Walkthrough

Recent-item records now retain label fields used by feature flags, notebooks, groups, and standard items. Feature-flag recents without a key are filtered according to picker mode and property-filter completeness. Local-list and popover labels use group-specific fallback values. FlagSelector resolves feature-flag IDs and keys, and survey editing stores a supplied or fetched flag object.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 472fb

Reselecting a flag while its earlier lookup is pending can undo the newer choice. Invalidate pending lookups on each selection before merging, or accept this bounded risk.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 472fb

The change preserves survey targeting during temporary lookup failures and does not demonstrate new privileges or cross-project access. Repeated selections can still admit stale responses, and editor-lifecycle behavior remains incompletely established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated mutable-state scope is the edited survey's linked flag and variant selection. Persisting a stale clearing transition could remove that survey's flag targeting. No attacker-driven cross-project access, privilege escalation, or broader independently attackable scope was established.

Trust Boundaries and Controls

  • observed — The picker-supplied identifier crosses into a feature-flag API read through the shared request client. Local shape checks and the selected-ID completion guard are present, but neither establishes authorization. Backend ownership enforcement for the read and survey update remains unverified.

Resilience and Maintainability Implications

  • observed — Different-ID and cleared-selection completions are ignored, and retryable failures preserve targeting. The helper has no explicit request-generation or unmount guard; whether the external mounted-logic hook prevents post-unmount mutation was not established.

Hardening Proposals

  • proposed — Bind lookup completion and retry to a selection-generation token as well as the flag ID. Invalidate prior generations on every selection, including full-object selection, clearing, and editor disposal, so stale recovery cannot remove newer targeting.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is complete and self-contained. It explains the problem, user-visible changes, testing and limitations, release status, documentation decision, and agent context. It includes screensho…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx (1)

759-764: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a FeatureFlags popover header regression test.

The DefinitionPopover tests and stories do not render a FeatureFlags item. A test elsewhere confirms that { name: '', key: 'checkout-redesign' } displays the key in a recent-filter row, but it does not exercise ControlledDefinitionPopover.

A regression that removes the group.getName?.(item) fallback can therefore leave the popover title empty without a test failure. Add a focused popover assertion for a FeatureFlags item with an empty or absent name and a present key.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 56c9abc2-dbba-4c89-b087-234b426ed2d7

📥 Commits

Reviewing files that changed from the base of the PR and between b786130 and a91475f.

📒 Files selected for processing (11)
  • frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx
  • frontend/src/lib/components/FlagSelector.test.tsx
  • frontend/src/lib/components/FlagSelector.tsx
  • frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx
  • frontend/src/lib/components/TaxonomicFilter/TaxonomicFilterKeyOnly.test.tsx
  • frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts
  • frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.test.ts
  • frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts
  • frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.test.ts
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

A saved replay filter labels itself with `derived_name` when it has no name, and a group labels itself with `group_key`. Both fields were dropped when a pick was stored as a recent, so those rows read "Unnamed" or stayed blank for the same reason a feature flag did.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805
@trunk-io

trunk-io Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
Scenes-App/SidePanels SidePanelNotebooks smoke-test The test timed out while waiting for loading indicators or spinners to disappear. Logs ↗︎
compareTopLevelSections() reports a modifiers change when the current query overrides the team default A TypeError occurred because the code attempted to access the 'add' property of an undefined object. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

The flag picker handed callers whatever row was clicked. A row from the Feature Flags list is the flag; a row from the Recent category is a stored summary that holds only what labels it. Callers read the rest off that object, so a pick from Recent produced a broken link rather than a visibly empty one.

A replay trigger persists `{id, key}` to the team and the key was undefined, so the trigger matched nothing. The survey editor stored the summary as `linked_flag`, leaving the variant selector without a flag to read.

`pickedFeatureFlag` now resolves the id (falling back to the value the picker keys the row by), requires a key, and returns the flag only when the row really is one. The third `onChange` argument is optional, so a caller that needs the whole flag loads it by id, which the product tour field already did and the survey editor now does too.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/lib/components/FlagSelector.test.tsx (1)

45-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the change-history comment.

Describe the invariant this test protects: a Recent summary has no flag configuration and must not be returned as flag. Remove the account of what the previous behavior let callers store. As per coding guidelines, “Never record how the code got here.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 4fa7e98f-c784-4c73-ab57-27dabf0bd880

📥 Commits

Reviewing files that changed from the base of the PR and between ee5620d and 36dc61c.

📒 Files selected for processing (3)
  • frontend/src/lib/components/FlagSelector.test.tsx
  • frontend/src/lib/components/FlagSelector.tsx
  • frontend/src/scenes/surveys/SurveyEdit.tsx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Comment thread frontend/src/scenes/surveys/SurveyEdit.tsx Outdated
@phillram phillram changed the title fix(flags): label recent feature flags with their key fix(flags): make recent feature flags show and link correctly Oct 2, 2026
@phillram
phillram marked this pull request as ready for review October 2, 2026 19:08
@phillram phillram added the reviewhog ($$$) Reviews pull requests before humans do label Oct 2, 2026
@parameterai

parameterai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Risk: No findings

The delta since the last review restructures the survey editor's linked-flag picking: async flag lookups are now guarded with an isStillSelected check and the onChange handler always loads the flag by id when the picker hands back only a summary. The change is frontend-only UI state handling with no security surface — inputs are the user's own picks and a project-scoped API call — and no issues were found.

Sentinel reviewed 472fb46 · Review settings

@posthog

posthog Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 2 should fix, 1 consider.

Published 3 findings (view the review).

Resolved comments: 3 fixed, 1 declined, 1 already settled, 1 left for you

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 472fb46 · box box-8de83c0765ec · ready in 802s (push → usable) · build log · rebuilds on every push, torn down on close

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team October 2, 2026 19:09
Comment thread frontend/src/scenes/surveys/SurveyEdit.tsx Outdated
@posthog

posthog Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review

Found 2 should fix, 1 consider.

Comment thread frontend/src/scenes/surveys/SurveyEdit.tsx Outdated
Comment on lines +99 to +100
setSelectedFlag({ id: picked.id, label: picked.key })
onChange(picked.id, picked.key, picked.flag)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve Recent flag keys before saving replay triggers

should_fix bug

Issue description

Recent entries now store a flag key for up to 30 days. If the flag is renamed, selecting its Recent entry sends the old key through onChange. FlagTriggerSelector and triggerGroupFormLogic.addFlag persist that key without resolving the flag ID. The recording configuration forwards the stored key to SDKs unchanged. The replay trigger therefore uses the old key instead of the selected flag's current key. The live lookup updates only the button label and does not correct the saved trigger.

Why we think it's a valid issue
  • Checked: I read pickedFeatureFlag and the onChange wiring in frontend/src/lib/components/FlagSelector.tsx:39-49,92-101, and the recent-item storage in frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts. I read both replay callers: FlagTrigger/Selector.tsx and triggerGroups/triggerGroupFormLogic.ts. On the backend I read validate_session_recording_linked_flag in posthog/api/team.py, the SDK payload builder in posthog/models/remote_config.py, products/feature_flags/backend/session_recording_links.py, and the repair_replay_linked_flag_keys command.
  • Found: A recent is a snapshot of the row at pick time. It sits in localStorage per team for RECENT_FILTER_MAX_AGE_MS = 30 days (recentTaxonomicFiltersLogic.ts:19,258,287), and pickMinimalRecentItem copies key as it was then (:48-55). Nothing re-checks a flag recent against live data before selection. suggestedContextFilters.ts:35-39 only drops recents that have no key. pickedFeatureFlag then passes the stored row.key unchanged (FlagSelector.tsx:44), and onChange(picked.id, picked.key, picked.flag) sends it to the caller (:100).
  • Found: Both replay callers save that key without resolving it. FlagTriggerSelector calls onChange({ id, key, variant: null }), which writes team.session_recording_linked_flag. addFlag stores the key string as the trigger group's flag (triggerGroupFormLogic.ts:381-383). validate_session_recording_linked_flag (posthog/api/team.py:1595-1622) checks the dict shape and normalizes id, but does not look up the key from the id. remote_config.py:155-162 sends only the stored key to the SDKs as linkedFlag.
  • Found: The codebase records that a stale key is a severe failure. The session_recording_links.py:1-13 docstring says the SDKs "treat a flag they can't resolve as 'do not record', so a stale key silently turns replay off for the team". The rename rewrite relink_teams_on_key_change runs only when the rename happens, so it cannot fix a stale key written later. The repair command covers only the linked-flag column, and only when someone runs it. Its comments say it "does not read trigger groups, so a group left here stays stale".
  • Found: The UI hides the mismatch in the V1 path. flagSelectorButtonLabel ranks flagKey from the live featureFlagLogic({ id: value }) lookup above the picked label. So the button shows the flag's current key while the team row stores the old one.
  • Impact: The trigger chain is: pick flag X anywhere, rename X's key within 30 days, then pick X from Recent in replay settings. With that chain, the team's recording gate points at a key that no longer exists, and session replay stops for the whole team with no error. This PR adds this writer of stale keys. Before the PR, a Recent pick sent no key at all, so the PR improves the common case but leaves this gap. That gap contradicts the PR's claim that a flag picked from Recent now links correctly. The fix is small: when picked.flag is absent, resolve the current key by id before calling onChange. FlagSelector already loads the flag by id. The trigger needs a key rename, which is not common, but the consequence is silent, project-wide data loss in a path the PR targets on purpose. So should_fix holds.
Suggested fix

Resolve a Recent summary by ID before calling onChange. Pass the fetched flag's current key, for example onChange(currentFlag.id, currentFlag.key, currentFlag). Reuse the loaded featureFlag only when its ID matches picked.id. Refresh the stored Recent entry with the resolved key.

Prompt to fix with AI (copy-paste)
## Context
@frontend/src/lib/components/FlagSelector.tsx#L99-100

<issue_description>
Recent entries now store a flag key for up to 30 days. If the flag is renamed, selecting its Recent entry sends the old key through onChange. FlagTriggerSelector and triggerGroupFormLogic.addFlag persist that key without resolving the flag ID. The recording configuration forwards the stored key to SDKs unchanged. The replay trigger therefore uses the old key instead of the selected flag's current key. The live lookup updates only the button label and does not correct the saved trigger.
</issue_description>

<issue_validation>
- **Checked:** I read `pickedFeatureFlag` and the `onChange` wiring in `frontend/src/lib/components/FlagSelector.tsx:39-49,92-101`, and the recent-item storage in `frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts`. I read both replay callers: `FlagTrigger/Selector.tsx` and `triggerGroups/triggerGroupFormLogic.ts`. On the backend I read `validate_session_recording_linked_flag` in `posthog/api/team.py`, the SDK payload builder in `posthog/models/remote_config.py`, `products/feature_flags/backend/session_recording_links.py`, and the `repair_replay_linked_flag_keys` command.
- **Found:** A recent is a snapshot of the row at pick time. It sits in localStorage per team for `RECENT_FILTER_MAX_AGE_MS` = 30 days (`recentTaxonomicFiltersLogic.ts:19,258,287`), and `pickMinimalRecentItem` copies `key` as it was then (`:48-55`). Nothing re-checks a flag recent against live data before selection. `suggestedContextFilters.ts:35-39` only drops recents that have no key. `pickedFeatureFlag` then passes the stored `row.key` unchanged (`FlagSelector.tsx:44`), and `onChange(picked.id, picked.key, picked.flag)` sends it to the caller (`:100`).
- **Found:** Both replay callers save that key without resolving it. `FlagTriggerSelector` calls `onChange({ id, key, variant: null })`, which writes `team.session_recording_linked_flag`. `addFlag` stores the key string as the trigger group's `flag` (`triggerGroupFormLogic.ts:381-383`). `validate_session_recording_linked_flag` (`posthog/api/team.py:1595-1622`) checks the dict shape and normalizes `id`, but does not look up the key from the id. `remote_config.py:155-162` sends only the stored `key` to the SDKs as `linkedFlag`.
- **Found:** The codebase records that a stale key is a severe failure. The `session_recording_links.py:1-13` docstring says the SDKs "treat a flag they can't resolve as 'do not record', so a stale key silently turns replay off for the team". The rename rewrite `relink_teams_on_key_change` runs only when the rename happens, so it cannot fix a stale key written later. The repair command covers only the linked-flag column, and only when someone runs it. Its comments say it "does not read trigger groups, so a group left here stays stale".
- **Found:** The UI hides the mismatch in the V1 path. `flagSelectorButtonLabel` ranks `flagKey` from the live `featureFlagLogic({ id: value })` lookup above the picked label. So the button shows the flag's current key while the team row stores the old one.
- **Impact:** The trigger chain is: pick flag X anywhere, rename X's key within 30 days, then pick X from Recent in replay settings. With that chain, the team's recording gate points at a key that no longer exists, and session replay stops for the whole team with no error. This PR adds this writer of stale keys. Before the PR, a Recent pick sent no key at all, so the PR improves the common case but leaves this gap. That gap contradicts the PR's claim that a flag picked from Recent now links correctly. The fix is small: when `picked.flag` is absent, resolve the current key by id before calling `onChange`. `FlagSelector` already loads the flag by id. The trigger needs a key rename, which is not common, but the consequence is silent, project-wide data loss in a path the PR targets on purpose. So `should_fix` holds.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Resolve a Recent summary by ID before calling onChange. Pass the fetched flag's current key, for example onChange(currentFlag.id, currentFlag.key, currentFlag). Reuse the loaded featureFlag only when its ID matches picked.id. Refresh the stored Recent entry with the resolved key.
</potential_solution>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Escalated: the issue is real, but the fix site is a design choice for the PR author.


  • Confirmed: a Recent flag entry keeps its key for up to 30 days, and both replay writers save that key without a lookup. The backend relinks only at rename time, so a stale key turns replay off for the team.
  • Option A: FlagSelector loads the flag by id for a Recent pick before it calls onChange. This is one change, but onChange becomes async on every surface and this PR's "callers load by id" design is reversed.
  • Option B: only the team linked flag and the trigger group addFlag load the flag by id when flag is absent, then save its current key. This keeps the PR's design.
  • Option C: refresh or drop flag Recents against live flags, so a stale key never reaches a caller.
More detail
  • Each option needs a guard so a late lookup cannot replace a newer pick. No test covers that flow now, so I did not make the change unattended.
How this was verified

No code change, so I ran no tests. I checked the code paths by reading FlagSelector.tsx, FlagTrigger/Selector.tsx, triggerGroupFormLogic.ts, posthog/api/team.py, posthog/models/remote_config.py and products/feature_flags/backend/session_recording_links.py at the current head.

@posthog posthog Bot removed the reviewhog ($$$) Reviews pull requests before humans do label Oct 2, 2026
A flag picked from Recent is loaded by id so the variant selector can read it. Any failed lookup cleared linked_flag_id, so a temporary error followed by a save removed the survey's flag targeting.

Only a 404 now ends the link, and it clears linked_flag with it. Any other failure keeps the pick and shows an error toast with a button that retries the lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 77ee946d-952c-4c91-95bc-424e31f3839d
@github-actions
github-actions Bot requested a deployment to preview-pr-110227 October 2, 2026 19:34 In progress
The drop for keyless flag recents also removed completed flag dependencies. Both property-filter writers store those without `key`, and their row takes its label from the property filter, so they never rendered blank.

A keyless flag recent is now dropped only when it has no completed property filter, or when the picker selects a key only and so strips that filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 77ee946d-952c-4c91-95bc-424e31f3839d
@github-actions
github-actions Bot requested a deployment to preview-pr-110227 October 2, 2026 19:40 In progress
A flag picked from Recent is loaded by id. If the user picked another flag or cleared the field before that lookup finished, its result wrote the old flag into the newer selection, and a failure cleared the newer selection.

The lookup now applies its result only while its flag is still the selected one. A Recent pick clears the loaded flag first, so the previous flag's variants do not show for the new pick while the lookup runs.

Every pick now goes through that one path. The branch for a selected id with no loaded flag fetched the previous id after the pick changed, so it could write the old flag into the new selection and kept the old variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 77ee946d-952c-4c91-95bc-424e31f3839d

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
frontend/src/scenes/surveys/SurveyEdit.tsx-380-406 (1)

380-406: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Invalidate pending flag lookups on every selection.

The guard checks only linked_flag_id. If Recent A starts a lookup, selecting A again or selecting full A leaves that ID unchanged. The older lookup can then set its older flag object, or a 404 can clear the newer selection. Track a selection generation and require both the ID and generation to match before applying the result.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: ab868ce2-a0ef-4e01-99bb-f143cca97ebf

📥 Commits

Reviewing files that changed from the base of the PR and between 36dc61c and 472fb46.

📒 Files selected for processing (3)
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.test.ts
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts
  • frontend/src/scenes/surveys/SurveyEdit.tsx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 3 remain after this review.

This branch was successfully deployed

1 active deployment
preview-pr-110227 — 472fb46b Deployed Oct 2, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant